#!/bin/python
# coding=utf-8

from PyQt5.QtGui import QTextCursor, QFont, QColor, QWheelEvent, QKeyEvent, QDropEvent, QDragEnterEvent, \
    QTextCharFormat, QClipboard
from PyQt5.QtWidgets import QVBoxLayout, QSplitter, QLabel, QWidget, QCheckBox, QLineEdit, QListWidget, QListWidgetItem, \
    QMenu, QAction, QProgressBar, QDateTimeEdit, QGroupBox, QSpacerItem, QDockWidget, QApplication

from common.func import showMessageBox, CustomLineEdit
from config import get_state, update_state, GLOBAL_STATE

# --------------------------------------------------
# 方案 A：分页 + mmap 的只读大文件查看器
# --------------------------------------------------
from PyQt5.QtWidgets import QVBoxLayout, QSplitter, QLabel, QWidget, QCheckBox, QLineEdit

from PyQt5.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QFileDialog, QMessageBox,
    QSizePolicy, QLabel, QPushButton, QPlainTextEdit)
from PyQt5.QtCore import Qt, pyqtSignal, QDateTime
from config import get_state, update_state
import os
import mmap
import re
from datetime import datetime, timedelta
from bisect import bisect_left
from PyQt5.QtWidgets import QPlainTextEdit
from PyQt5.QtCore import Qt

import time
from functools import wraps
from logger import log_info, log_warning, log_error, log_critical

size = GLOBAL_STATE['settings']['font_size']
font_small = QFont()
font_small.setPointSize(12)
font_mid = QFont()
font_mid.setPointSize(size)
font_bold = QFont()
font_bold.setPointSize(size)
font_bold.setBold(True)
LINEEDIT_STYLE = """
QLineEdit {
    font: 12pt "Arial";
    color: black;
    background-color: white;
    border: 1px solid gray;
    padding: 2px;
}
"""

def format_size(file_size):
    # 格式化文件大小
    if file_size < 1024:
        size_str = f"{file_size} 字节"
    elif file_size < 1024 * 1024:
        size_str = f"{file_size / 1024:.2f} KB"
    elif file_size < 1024 * 1024 * 1024:
        size_str = f"{file_size / (1024 * 1024):.2f} MB"
    else:
        size_str = f"{file_size / (1024 * 1024 * 1024):.2f} GB"

    return size_str


def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()  # 记录开始时间
        result = func(*args, **kwargs)  # 调用原函数
        end_time = time.time()  # 记录结束时间
        elapsed_time = end_time - start_time  # 计算执行时间
        log_info(f"Function '{func.__name__}' executed in {elapsed_time:.6f} seconds")
        return result

    return wrapper


class PagedPlainEdit(QPlainTextEdit):
    # chunk_bytes = 1024 * 1024 * 5  # 每个块的大小（字节）
    chunk_bytes = GLOBAL_STATE['settings']['file_split_size']
    MAX_CACHE = 10  # 最大缓存块数
    # 添加一个信号，用于同步滚动事件
    scroll_sync = pyqtSignal(int)
    # 添加一个信号，用于更新进度条
    progress_update = pyqtSignal(int)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setReadOnly(True)
        self.setLineWrapMode(QPlainTextEdit.NoWrap)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.setFont(font_mid)
        self.setStyleSheet("""
        QPlainTextEdit {
            selection-background-color: #FFA07A; /* 选中文字的背景颜色 */
            selection-color: #000000; /* 选中文字的字体颜色 */
        }
        """)

        self.mmap_obj = None
        self.total_lines = 0  # 总行数
        self.chunks = []  # 存储每个块的起始偏移和时间范围
        self.current_chunk_index = 0  # 当前显示的块索引
        self.time_pattern = None
        # 缓存机制
        self.cache = []  # 缓存列表，存储 (chunk_index, chunk_content) 对
        self.cache_indices = set()  # 缓存的块索引集合，用于快速查找

        # 添加右键菜单
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_context_menu)

    def wheelEvent(self, event: QWheelEvent):
        # 捕获鼠标滚轮事件
        delta = event.angleDelta().y()
        # 判断滚动方向
        if delta > 0:
            print("向上滚动")
        elif delta < 0:
            print("向下滚动")
        # 调整滚动速度
        print(delta)
        delta = delta // 3  # 将滚动速度减慢
        print(delta)
        super().wheelEvent(event)  # 调用父类的 wheelEvent 方法
        self.scroll_sync.emit(-delta)  # 发射同步滚动信号，注意这里取负值

    def keyPressEvent(self, event: QKeyEvent):
        # 捕获键盘事件
        if event.key() == Qt.Key_Up:
            # 向上滚动
            scrollbar = self.verticalScrollBar()
            current_value = scrollbar.value()
            new_value = max(current_value - 1, 0)  # 每次向上滚动10个单位
            scrollbar.setValue(new_value)
            self.scroll_sync.emit(-1)  # 发射同步滚动信号
        elif event.key() == Qt.Key_Down:
            # 向下滚动
            scrollbar = self.verticalScrollBar()
            current_value = scrollbar.value()
            new_value = min(current_value + 1, scrollbar.maximum())  # 每次向下滚动10个单位
            scrollbar.setValue(new_value)
            self.scroll_sync.emit(1)  # 发射同步滚动信号
        else:
            super().keyPressEvent(event)  # 调用父类的 keyPressEvent 方法

    def show_context_menu(self, pos):
        menu = QMenu(self)
        cp_action = QAction("复制", self)
        cp_action.triggered.connect(self.copy_info_to_clipboard)
        jump_action = QAction("跳转相同时间", self)
        jump_action.triggered.connect(self.jump_to_same_time)
        start_search_action = QAction("从这里开始搜索", self)  # 添加新的右键菜单项
        start_search_action.triggered.connect(self.start_search_from_here)  # 连接信号

        menu.addAction(cp_action)
        menu.addAction(jump_action)
        menu.addAction(start_search_action)
        menu.exec_(self.viewport().mapToGlobal(pos))

    def copy_info_to_clipboard(self):
        # 将信息栏的内容复制到剪贴板
        clipboard = QApplication.clipboard()
        cursor = self.textCursor()
        selected_text = cursor.selectedText()
        clipboard.setText(selected_text, QClipboard.Clipboard)

    def jump_to_same_time(self):
        if not self.mmap_obj:
            return

        cursor = self.textCursor()
        cursor.select(QTextCursor.LineUnderCursor)
        current_line_content = cursor.selectedText()
        ts = self._parse_time(current_line_content)
        if ts is None:
            log_error("Could not find timestamp for line:{}".format(current_line_content))
            self.parent().label_msg.setText("当前行没有时间戳.")
            return

        # 发射信号，通知其他面板跳转
        if hasattr(self.parent(), 'sync_requested'):
            self.parent().sync_requested.emit(ts)
        else:
            log_error("Parent widget does not have 'sync_requested' signal")

    def start_search_from_here(self):
        cursor = self.textCursor()
        cursor.select(QTextCursor.LineUnderCursor)
        current_line_content = cursor.selectedText()
        # 更新 self.last_search_cursor 为当前行号
        self.parent().last_search_cursor = cursor.position()
        # 可以在这里添加更多逻辑，比如开始搜索等
        log_info(f"开始从这里搜索: {current_line_content}")

    def _infer_time_pattern(self, lines: list):
        patterns = [
            r'(\d{8}:\d{2}:\d{2}:\d{2})',  # 20250804:10:53:30
            r'(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}(?:,\d{3})?)',
            r'(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?)',
            r'\[(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})\]',
            r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}Z?)',
        ]
        for first_line in lines:
            for pat in patterns:
                if re.search(pat, first_line):
                    self.time_pattern = re.compile(pat)
                    log_info(f"Time pattern inferred: {pat}")
                    return True
        log_error(f"Could not infer time pattern from: {first_line}")
        return False

    def _parse_time(self, line: str) -> datetime:
        if not self.time_pattern:
            return None
        match = self.time_pattern.match(line)
        if not match:
            return None
        ts_str = match.group(1)
        try:
            if len(ts_str) == 17 and ts_str.count(':') == 3:
                return datetime.strptime(ts_str, "%Y%m%d:%H:%M:%S")
            elif ',' in ts_str:
                return datetime.strptime(ts_str.split(',')[0], "%Y-%m-%d %H:%M:%S")
            elif len(ts_str) == 19 and ts_str.count(' ') == 1:
                return datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S")
            else:
                return datetime.strptime(ts_str, "%Y-%m-%dT%H:%M:%SZ")
        except ValueError as e:
            log_info(f"Error parsing timestamp '{ts_str}': {e}")
            return None

    def get_file_info(self):
        # 返回文件信息:大小和总行数
        if self.mmap_obj:
            return self.mmap_obj.size(), self.total_lines
        else:
            return 0, 0

    def load_file_sub(self, path):
        run_start_time = time.time()
        try:
            if self.mmap_obj:
                self.mmap_obj.close()
            self.clear()
            self.chunks = []
            self.total_lines = 0
            self.current_chunk_index = 0
            self.cache = []  # 清空缓存
            self.cache_indices = set()

            with open(path, 'rb') as f:
                self.mmap_obj = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
                file_size = self.mmap_obj.size()
                offset = 0
                while offset < self.mmap_obj.size():
                    chunk = self.mmap_obj[offset:offset + self.chunk_bytes].decode('utf-8', errors='ignore')
                    lines = chunk.splitlines()

                    # 智能分析下时间正则表达式
                    if not self.time_pattern:
                        if not self._infer_time_pattern(lines):
                            log_info("此chunk未找到时间戳")
                            offset += self.chunk_bytes
                            continue
                    # 解析时间范围
                    start_time = None
                    for line in lines:
                        ts = self._parse_time(line)
                        if ts:
                            start_time = ts
                            break

                    end_time = None
                    for line in reversed(lines):
                        ts = self._parse_time(line)
                        if ts:
                            end_time = ts
                            break

                    if start_time is None or end_time is None:
                        log_info(
                            f"Chunk at offset {offset} has invalid time range: start_time={start_time}, end_time={end_time}")
                    #     continue

                    # 每个块的条数也记录一下,用于后面查找具体的行也能用二分查找法
                    self.chunks.append((offset, start_time, end_time, len(lines)))
                    self.total_lines += len(lines)
                    offset += self.chunk_bytes

                    # 更新进度条
                    progress = int((offset / file_size) * 100)
                    self.progress_update.emit(progress)

            # 加载并显示第一个块
            self.load_chunk(0)
            log_info("chunks:{}".format(len(self.chunks)))
        except Exception as e:
            raise e
        finally:
            run_end_time = time.time()
            elapsed_time = run_end_time - run_start_time
            log_info(f"load_file_sub 函数耗时: {elapsed_time:.6f} 秒")

    @timeit
    def load_chunk(self, chunk_index):
        if chunk_index < 0 or chunk_index >= len(self.chunks):
            return

        # 检查缓存中是否存在
        if chunk_index in self.cache_indices:
            for i, (idx, content) in enumerate(self.cache):
                if idx == chunk_index:
                    log_info("{} cache 命中".format(chunk_index))
                    self.setPlainText(content)
                    self.current_chunk_index = chunk_index
                    # 将最近使用的块移动到缓存的最前面
                    self.cache.pop(i)
                    self.cache.insert(0, (idx, content))
                    return

        # 缓存中没有，从文件中读取并解码
        offset, _, _, _ = self.chunks[chunk_index]
        chunk = self.mmap_obj[offset:offset + self.chunk_bytes].decode('utf-8', errors='ignore')
        self.setPlainText(chunk)
        self.current_chunk_index = chunk_index

        # 添加到缓存
        if len(self.cache) >= self.MAX_CACHE:
            self.cache_indices.remove(self.cache[-1][0])  # 移除最旧的块索引
            self.cache.pop()  # 移除最旧的块
        self.cache.insert(0, (chunk_index, chunk))  # 将新块添加到缓存最前面
        self.cache_indices.add(chunk_index)  # 添加块索引到集合

    @timeit
    def jump_to_line(self, target: datetime, block_lines: int):
        if not self.chunks:
            return

        cur_chunk = self.cache[0][1]  # 从缓存中取出chunk的内容
        lines = cur_chunk.splitlines()
        # 找到第一条有效行
        left = 0
        # 找到最后一条有效行
        right = len(lines) - 1

        # 二分只走有效区间
        best = left
        while left <= right:
            mid = (left + right) // 2
            t = self._parse_time(lines[mid])
            if t is None:  # 跳过无效行，向左或右都行
                left += 1
                continue
            if t < target:
                best = mid
                left = mid + 1
            elif t > target:
                right = mid - 1
            else:  # 精确命中
                best = mid
                break
        self.parent().label_msg.setText("find line:{}".format(best))
        cursor = self.textCursor()
        cursor.movePosition(QTextCursor.Start)
        for _ in range(best):
            cursor.movePosition(QTextCursor.NextBlock)
        cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor)  # 选中整行
        self.setTextCursor(cursor)
        self.ensureCursorVisible()

        # # 如果找到目标行，设置光标位置
        # if left <= right:
        #     cursor = self.textCursor()
        #     cursor.movePosition(QTextCursor.Start)
        #     for _ in range(best):
        #         cursor.movePosition(QTextCursor.NextBlock)
        #     self.setTextCursor(cursor)
        #     self.ensureCursorVisible()
        # else:
        #     # 如果没有找到目标行，打印错误信息
        #     log_info(f"Could not find line with timestamp within")

    def close_file(self):
        if self.mmap_obj:
            self.mmap_obj.close()
            self.mmap_obj = None
        self.clear()
        self.chunks = []
        self.total_lines = 0
        self.current_chunk_index = 0
        self.cache = []  # 清空缓存
        self.cache_indices = set()


# --------------------------------------------------
# 原有的 FilePanel，仅把 text 控件换掉
# --------------------------------------------------
class FilePanel(QWidget):
    file_loaded = pyqtSignal(str)
    sync_requested = pyqtSignal(datetime)  # 添加同步信号
    scroll_sync = pyqtSignal(int)  # 添加滚动同步信号

    def __init__(self, title="文件", parent=None):
        super().__init__(parent)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        self.title = title
        self.file_path = None
        self.chunk_info = []  # 新增保存chunks信息的列表
        self.init_ui()
        self.last_search_cursor = None  # 添加记录上次搜索位置的变量

    @timeit
    def jump_to_time(self, target: datetime):
        if not self.text.mmap_obj:
            return

        # 二分查找定位块
        start_times = [block[1] for block in self.chunk_info]
        block_index = bisect_left(start_times, target)

        # 检查索引是否有效
        if block_index >= len(self.chunk_info):
            block_index = len(self.chunk_info) - 1
        elif block_index > 0 and self.chunk_info[block_index][1] > target:
            block_index -= 1

        block = self.chunk_info[block_index]
        offset, start_ts, end_ts, block_lines = block

        if target < start_ts:
            self.label_msg.setText("未找到相近时间段日志")
            return
        if target > end_ts:
            self.label_msg.setText("未找到相近时间段日志")
            return
        # 加载对应的块
        if self.text.current_chunk_index != block_index:
            # 相同的块号就不再load了
            self.text.load_chunk(block_index)

        # 再找行
        self.text.jump_to_line(target, block_lines)

    def current_top_line(self):
        """PagedPlainEdit 每次 jump_to_page 的 start_line 就是页首"""
        return getattr(self.text, "_last_start_line", 0)

    def init_ui(self):
        pub_height = 20
        lay = QVBoxLayout(self)
        lay.setContentsMargins(0, 0, 0, 0)
        lay.setSpacing(5)  # 设置布局之间的间距

        # 第一行布局：仅包含 self.label
        label_layout = QHBoxLayout()
        label_layout.setContentsMargins(0, 0, 0, 0)
        label_layout.setSpacing(5)

        self.label = QLabel(f"未选择:{self.title}")
        self.label.setAlignment(Qt.AlignLeft)
        self.label.setFont(font_bold)

        label_layout.addWidget(self.label)

        # 第二行布局：包含其他控件
        bottom_row = QHBoxLayout()
        bottom_row.setContentsMargins(0, 0, 0, 0)
        bottom_row.setSpacing(5)

        # 第3行布局：包含其他控件
        third_row = QHBoxLayout()
        third_row.setContentsMargins(0, 0, 0, 0)
        third_row.setSpacing(5)

        self.third_row_btn = QPushButton("▼")
        self.third_row_btn.setFixedWidth(50)
        self.third_row_btn.clicked.connect(self.panel_third_row_btn)

        self.label_msg = QLabel()
        self.label_msg.setAlignment(Qt.AlignCenter)
        self.label_msg.setFont(font_mid)
        self.label_msg.setStyleSheet("color: red;")  # 使用样式表设置文本颜色为红色


        self.reload_btn = QPushButton("重载文件")
        self.reload_btn.clicked.connect(self.reload_file)

        self.btn = QPushButton("选择文件")
        self.btn.clicked.connect(self.select_file)

        self.file_info = QLineEdit("")
        self.file_info.setFont(font_small)
        self.file_info.setFixedWidth(300)
        self.file_info.setFixedHeight(pub_height)
        self.file_info.setReadOnly(True)
        self.file_info.setPlaceholderText("文件信息")

        self.progress_bar = QProgressBar(self)
        self.progress_bar.setFixedWidth(80)
        self.progress_bar.setFixedHeight(pub_height)
        self.progress_bar.setValue(0)

        # 添加一个按钮来切换换行模式
        self.toggle_wrap_mode_btn = QPushButton("切换换行模式")
        self.toggle_wrap_mode_btn.clicked.connect(self.toggle_wrap_mode)

        bottom_row.addWidget(self.third_row_btn)
        bottom_row.addWidget(self.reload_btn)
        bottom_row.addWidget(self.btn)
        bottom_row.addWidget(self.file_info)
        bottom_row.addWidget(self.progress_bar)
        bottom_row.addWidget(self.toggle_wrap_mode_btn)  # 添加切换换行模式的按钮
        bottom_row.addWidget(self.label_msg)

        # 替换为 PagedPlainEdit
        self.text = PagedPlainEdit()
        self.text.scroll_sync.connect(self.sync_scroll)  # 连接滚动同步信号
        self.text.progress_update.connect(self.update_progress)  # 连接进度条更新信号

        # 添加QListWidget控件
        self.chunk_list = QListWidget()
        self.chunk_list.itemDoubleClicked.connect(self.chunk_item_clicked)  # 绑定点击事件
        self.chunk_list.setFont(font_small)


        # 布局
        main_layout = QHBoxLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(5)
        main_layout.addWidget(self.text, 3)

        lay.addLayout(label_layout)
        lay.addLayout(bottom_row)
        lay.addLayout(third_row)
        lay.addLayout(main_layout)

        # 创建一个浮动窗口
        self.dock_widget = QDockWidget("Chunk List", self)
        self.dock_widget.setWidget(self.chunk_list)
        self.dock_widget.setFloating(True)
        self.dock_widget.hide()
        # 为 chunk_list_groupbox 重写 focusOutEvent
        self.chunk_list.focusOutEvent = self.on_chunk_list_groupbox_focus_out

        # 启用拖放
        self.setAcceptDrops(True)

    def on_chunk_list_groupbox_focus_out(self, event):
        # 调用原始的 focusOutEvent 方法
        super(type(self.chunk_list), self.chunk_list).focusOutEvent(event)
        # 关闭浮动窗口
        self.dock_widget.hide()
        self.third_row_btn.setText("▼")

    def toggle_wrap_mode(self):
        """切换换行模式"""
        if self.text.lineWrapMode() == QPlainTextEdit.NoWrap:
            self.text.setLineWrapMode(QPlainTextEdit.WidgetWidth)
            self.toggle_wrap_mode_btn.setText("切换到无换行")
        else:
            self.text.setLineWrapMode(QPlainTextEdit.NoWrap)
            self.toggle_wrap_mode_btn.setText("切换到自动换行")

    def update_progress(self, value):
        """更新进度条的值"""
        self.progress_bar.setValue(value)

    def sync_scroll(self, delta):
        """同步滚动"""
        if abs(delta) > 1:
            delta = delta // 6
        if self.text.hasFocus():
            return  # 如果当前面板有焦点，不处理同步滚动
        scrollbar = self.text.verticalScrollBar()
        print("scrollbar.value():{}".format(scrollbar.value()))
        value = scrollbar.value() + delta
        print("new value:{}".format(value))
        scrollbar.setValue(value)

    def get_current_line_content(self):
        cursor = self.text.textCursor()  # 获取当前光标位置
        cursor.select(QTextCursor.LineUnderCursor)  # 选择当前光标所在的行
        current_line_content = cursor.selectedText()  # 获取选中的行内容
        ts = self.text._parse_time(current_line_content)  # 解析时间戳
        log_info("获取当前行：{}的时间是：{}".format(current_line_content, ts))
        if not ts:
            return current_line_content, None
        else:
            return current_line_content, ts

    def update_chunk_list(self, s=None, e=None):
        """更新chunk列表"""
        self.chunk_list.clear()
        for i, chunk in enumerate(self.chunk_info):
            start_time = chunk[1].strftime("%Y-%m-%d %H:%M:%S") if chunk[1] else "N/A"
            end_time = chunk[2].strftime("%Y-%m-%d %H:%M:%S") if chunk[2] else "N/A"
            if e and start_time != 'N/A' and chunk[1] > e:
                continue
            if s and end_time != 'N/A' and chunk[2] < s:
                continue
            item_text = f"Chunk {i}: {start_time} - {end_time}"
            item = QListWidgetItem(item_text)
            item.setData(Qt.UserRole, i)  # 保存块索引
            self.chunk_list.addItem(item)
            # 设置背景颜色
            if i % 2 == 0:
                item.setBackground(QColor("#e0f7fa"))  # 偶数行
            else:
                item.setBackground(QColor("#ffffff"))  # 奇数行

    def chunk_item_clicked(self, item):
        """处理chunk列表项点击事件"""
        chunk_index = item.data(Qt.UserRole)
        self.text.load_chunk(chunk_index)  # 加载对应的块内容

    def select_file(self, init_path=None):
        default_dir = get_state('settings', 'default_dir', '')
        if not init_path:
            path, _ = QFileDialog.getOpenFileName(
                self, f"选择{self.title}", default_dir,
                "所有文件 (*);;文本文件 (*.txt)")
        else:
            path = init_path
        if not path:
            return
        # 保存全局状态
        update_state('settings', 'default_dir', os.path.dirname(path))
        recent_files = get_state('settings', 'recent_files', [])
        if path in recent_files:
            recent_files.remove(path)
        recent_files.insert(0, path)
        update_state('recent_files', 'files', recent_files[:20])
        self.load_file(path)

    def load_file(self, path):
        try:
            self.progress_bar.setValue(0)  # 重置进度条
            self.text.load_file_sub(path)
            self.chunk_info = self.text.chunks  # 保存chunks信息
            self.update_chunk_list()  # 更新chunk列表

            # 获取文件大小,总行数
            file_byte_size, total_lines = self.text.get_file_info()
            file_size = format_size(file_byte_size)  # 格式化

            # 显示文件信息
            self.file_info.setText(f"文件大小:{file_size},总行数:{total_lines}")
            self.progress_bar.setValue(100)  # 设置进度条为100%
        except Exception as e:
            QMessageBox.warning(self, "错误", f"无法读取文件:\n{e}")
            return
        self.file_path = path
        self.label.setText(path)
        self.file_loaded.emit(path)

    def panel_third_row_btn(self):
        # 获取 self.text 的局部坐标
        local_pos = self.text.pos()
        # 将局部坐标转换为全局坐标
        global_pos = self.text.mapToGlobal(local_pos)
        # 获取主窗口的引用
        main_window = self.window()
        # 将全局坐标转换为相对于主窗口的坐标
        relative_pos = main_window.mapFromGlobal(global_pos)

        self.dock_widget.setFixedWidth(self.width() // 3)
        if self.dock_widget.isVisible():
            self.dock_widget.hide()
            self.third_row_btn.setText("▼")
        else:
            # 设置浮动窗口的位置和大小
            self.dock_widget.setGeometry(relative_pos.x(), relative_pos.y(), self.width() // 3, self.height() // 2 + 20)
            print(self.dock_widget.size())
            print(self.width())
            self.dock_widget.show()
            self.third_row_btn.setText("▲")

    def reload_file(self):
        if self.file_path:
            self.load_file(self.file_path)

    def close_file(self):
        """关闭文件句柄"""
        self.text.close_file()
        self.chunk_info = []  # 清空chunks信息
        self.chunk_list.clear()  # 清空chunk列表

    def dragEnterEvent(self, event: QDragEnterEvent):
        """处理拖入事件"""
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dragMoveEvent(self, event: QDragEnterEvent):
        """处理拖动事件"""
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event: QDropEvent):
        """"处理文件拖放事件"""
        if event.mimeData().hasUrls():
            url = event.mimeData().urls()[0]
            path = url.toLocalFile()
            self.load_file(path)
            event.accept()
        else:
            event.ignore()


class FileCompareWidget(QWidget):
    sync_requested = pyqtSignal(datetime)  # 参数：基准时间戳

    def __init__(self):
        super().__init__()
        self.init_ui()
        self._bind_signals()

    def init_ui(self):
        # 1. 两个文件面板改为水平布局
        self.top_panel = FilePanel("顶部文件")
        self.mid_panel = FilePanel("中部文件")

        # 连接信号
        self.top_panel.sync_requested.connect(self.sync_requested)
        self.mid_panel.sync_requested.connect(self.sync_requested)

        # 连接滚动同步信号
        # self.top_panel.text.scroll_sync.connect(self.mid_panel.sync_scroll)
        # self.mid_panel.text.scroll_sync.connect(self.top_panel.sync_scroll)

        self.file_split = QSplitter(Qt.Vertical)  # 改为水平分隔
        self.file_split.addWidget(self.top_panel)
        self.file_split.addWidget(self.mid_panel)
        self.file_split.setStretchFactor(0, 1)  # 让两个区域平分宽度
        self.file_split.setStretchFactor(1, 1)

        # 2. 控制区：移除实时同步开关和手动同步按钮
        ctrl_lay = QHBoxLayout()
        self.sync_checkbox = QCheckBox("启用同步滚动")
        self.sync_checkbox.setChecked(False)  # 默认启用同步滚动
        self.sync_checkbox.stateChanged.connect(self.toggle_sync)

        # 添加一个交换面板位置的按钮
        self.swap_panels_btn = QPushButton("交换面板位置")
        self.swap_panels_btn.clicked.connect(self.swap_panels)

        # 3. 添加一个时间选择的控件
        # --------- 新增 QLabel ---------
        self.label_time_range = QLabel("仅看时间范围：", self)
        # --------------------------------
        # 创建一个 QLineEdit 用于显示和编辑时间,默认7天前
        self.time_edit_start = QLineEdit(self)
        self.time_edit_start.setFont(font_small)
        self.time_edit_start.setFixedWidth(180)
        default_time = QDateTime.currentDateTime().addDays(-7)
        self.time_edit_start.setPlaceholderText("YYYY-MM-DD HH:MM:SS")  # 设置占位符
        self.time_edit_start.setText(default_time.toString("yyyy-MM-dd HH:mm:ss"))  # 设置默认时间为当前时间
        # --------- 新增 QLabel ---------
        self.label_mid = QLabel("～", self)
        # --------------------------------
        self.time_edit_end = QLineEdit(self)
        self.time_edit_end.setFont(font_small)
        self.time_edit_end.setFixedWidth(180)
        self.time_edit_end.setPlaceholderText("YYYY-MM-DD HH:MM:SS")  # 设置占位符
        self.time_edit_end.setText(QDateTime.currentDateTime().toString("yyyy-MM-dd HH:mm:ss"))  # 设置默认时间为当前时间
        # --------------------------------
        self.filter_time = QPushButton("过滤时间")
        self.filter_time.clicked.connect(self.fn_filter_time)
        self.filter_time_reset = QPushButton("重置")
        self.filter_time_reset.clicked.connect(self.fn_filter_time_reset)


        # 4. 加一个搜索内容编辑框
        self.search_edit = CustomLineEdit(title="searchLineEdit", parent=self, width=300,
                                          height=20, callback=self.search_text)

        # self.search_edit.setPlaceholderText("输入搜索内容")
        # self.search_edit.setFixedWidth(300)
        self.search_btn = QPushButton("搜索")
        self.search_btn.clicked.connect(self.search_text)

        # 布局
        ctrl_lay.addWidget(self.sync_checkbox)
        ctrl_lay.addWidget(self.search_edit)
        ctrl_lay.addWidget(self.search_btn)
        ctrl_lay.addWidget(self.swap_panels_btn)  # 添加交换面板位置的按钮

        ctrl_lay.addWidget(self.label_time_range)
        ctrl_lay.addWidget(self.time_edit_start)
        ctrl_lay.addWidget(self.label_mid)
        ctrl_lay.addWidget(self.time_edit_end)
        ctrl_lay.addWidget(self.filter_time)
        ctrl_lay.addWidget(self.filter_time_reset)
        ctrl_lay.addStretch()

        # 3. 主布局
        main = QVBoxLayout(self)
        main.setContentsMargins(0, 0, 0, 0)
        main.addLayout(ctrl_lay)
        main.addWidget(self.file_split)

    def fn_filter_time(self):
        # 过滤掉panel中不在时间范围内的chunk
        from datetime import datetime
        fmt = "%Y-%m-%d %H:%M:%S"  # 与输入框格式保持一致
        str_time = self.time_edit_start.text().strip()
        end_time = self.time_edit_end.text().strip()
        s_time = datetime.strptime(str_time, fmt)
        e_time = datetime.strptime(end_time, fmt)
        for panel in (self.top_panel, self.mid_panel):
            if panel.chunk_info:
                panel.update_chunk_list(s_time, e_time)

    def fn_filter_time_reset(self):
        default_time = QDateTime.currentDateTime().addDays(-7)
        self.time_edit_start.setText(default_time.toString("yyyy-MM-dd HH:mm:ss"))  # 设置默认时间为当前时间
        self.time_edit_end.setText(QDateTime.currentDateTime().toString("yyyy-MM-dd HH:mm:ss"))  # 设置默认时间为当前时间

    def swap_panels(self):
        """交换两个面板的位置"""
        # 打印当前面板的顺序
        # print("交换前顺序:", self.file_split.widget(0), self.file_split.widget(1))

        # 保存当前面板顺序
        temp = self.file_split.widget(0)
        self.file_split.insertWidget(0, self.file_split.widget(1))
        self.file_split.insertWidget(1, temp)

        # 打印交换后的面板顺序
        # print("交换后顺序:", self.file_split.widget(0), self.file_split.widget(1))

    def toggle_chunk_list(self):
        """切换Chunk列表的显示和隐藏"""
        self.top_panel.chunk_list.setVisible(not self.top_panel.chunk_list.isVisible())
        self.mid_panel.chunk_list.setVisible(not self.mid_panel.chunk_list.isVisible())

    def time_changed(self, dt):
        # 当时间选择控件的值改变时，同步两个面板到这个时间点
        self.sync_all_panels_to_time(dt)

    @timeit
    def sync_all_panels_to_time(self, dt):
        for panel in (self.top_panel, self.mid_panel):
            if panel.file_path:
                log_info(panel.title)
                panel.jump_to_time(dt)

    def search_text(self):
        search_text = self.search_edit.currentText()
        if not search_text:
            QMessageBox.warning(self, "警告", "请输入搜索内容")
            return
        for panel in (self.top_panel, self.mid_panel):
            if panel.file_path:
                self._search_in_panel(panel, search_text)

    def _search_in_panel(self, panel, search_text):
        document = panel.text.document()
        if panel.last_search_cursor is None:
            cursor = QTextCursor(document)
            cursor.movePosition(QTextCursor.Start)
        else:
            cursor = panel.last_search_cursor
        cursor = document.find(search_text, cursor)
        if not cursor.isNull():
            panel.text.setTextCursor(cursor)
            panel.text.ensureCursorVisible()
            panel.last_search_cursor = cursor  # 更新上次搜索位置
            panel.label_msg.clear()
        else:
            panel.label_msg.setText("未找到更多匹配的内容")
            panel.last_search_cursor = None  # 重置搜索位置

    def toggle_sync(self, state):
        """根据复选框的状态启用或禁用同步滚动"""
        if state == Qt.Checked:
            self.top_panel.text.scroll_sync.connect(self.mid_panel.sync_scroll)
            self.mid_panel.text.scroll_sync.connect(self.top_panel.sync_scroll)
        else:
            self.top_panel.text.scroll_sync.disconnect(self.mid_panel.sync_scroll)
            self.mid_panel.text.scroll_sync.disconnect(self.top_panel.sync_scroll)

    # ----------------- 内部辅助 ------------------
    def _bind_signals(self):
        self.sync_requested.connect(self._sync_all)

    def _make_mouse_handler(self, panel):
        def handler(event):
            type(panel.text).mousePressEvent(panel.text, event)

        return handler

    def _sync_all(self, ts):
        try:
            src_panel = next((p for p in (self.top_panel, self.mid_panel) if p.text.hasFocus()), None)
            if not src_panel:
                return
            for p in (self.top_panel, self.mid_panel):
                p.label_msg.clear()
                if p.file_path and p is not src_panel:
                    p.jump_to_time(ts)
        except Exception as e:
            log_error("{}:{}".format("_sync_call", str(e)))
